import { 
  FuturesAccountBalance,
  MainClient,
  USDMClient,
  CoinMClient,
  NewSpotOrderParams,
  NewFuturesOrderParams,
  OrderResult,
  SpotOrder,
  CancelFuturesOrderResult,
  CancelSpotOrderResult,
  NewOrderResult,
  generateNewOrderId,
  OrderSide,
  OrderResponseResult,
  UniversalTransferParams
} from 'binance';

import SymbolOrderConfigService from 'ROOT/module/symbol-order-config/SymbolOrderConfig.service';
import { SymbolOrderMoreConfig } from 'ROOT/module/symbol-order-config/SymbolOrderConfig.entity';
import { errLogger, outLogger } from "ROOT/common/logger";
import { SymbolBalance } from "./interface";
import { retryPromise } from "ROOT/common/retry";
import { BinanceCommon, DeliverySymbolInfo, FuturesSymbolInfo, SymbolInfo } from "./BinanceCommon";
import { autoToFixed, keepDecimalFixed, singlePromise } from "ROOT/utils";
import { 
  OrderBook,
  PriceConverter,
  getSpotSymbol,
  parserBaseSymbol,
  parserQuoteSymbol,
  splitOrder,
  AutoOrderConfig,
  AutoTradeType,
  DataHandler,
  ExchangeTraderBase,
} from "ROOT/trader";
import AutoOrderHistoryEntity from 'ROOT/module/order-history/AutoOrderHistory.entity';
import { PositionSide } from 'ROOT/lib/quant';
import { robot } from "ROOT/common/DingRobot";

export const binancePriceConverter = new PriceConverter('binance');

export class BinanceTrader extends ExchangeTraderBase{

  /** 
 * @see https://github.com/tiagosiebler/binance/blob/master/src/main-client.ts
 */
  mainClient: MainClient
  /** 
   * @see https://github.com/tiagosiebler/binance/blob/master/src/usdm-client.ts
   */
  futureClient: USDMClient
  /** 
   * @see https://github.com/tiagosiebler/binance/blob/master/src/coinm-client.ts
   */
  deliveryClient: CoinMClient
  config: SymbolOrderMoreConfig = {} as SymbolOrderMoreConfig;
  dataHander: DataHandler;

  /**
   * @exmple { BTC: { free: '0.77206464', locked: '0.00177975' }, LTC: { free: '0.00000000', locked: '0.00000000' } }
   */
  private spotBalanceMap: Record<string, { free: string; locked: string }> = {};
  private deliveryBalanceMap: Record<string, SymbolBalance> = {};
  private futuresBalanceMap: Record<string, FuturesAccountBalance> = {};
  private binanceCommon: BinanceCommon;

  constructor(userId: number, binanceCommon: BinanceCommon, config: {
    dataHander?: DataHandler;
    access_key: string;
    secret_key: string;
    username: string;
  }) {
    super('binance', userId, {username: config.username});
    this.binanceCommon = binanceCommon;
    this.dataHander = config.dataHander;

    this.updateSDK({
      access_key: config.access_key,
      secret_key: config.secret_key,
    });
    // this.getDeliveryPositionAmt('BTCUSDT').then((data) => {
    //   console.log(data)
    // })
    this.riskModel.on('order', async ({ quantity, side, positionSide, markPrice, type, symbol, unRealizedProfit, tradeType }) => {
      if (tradeType === 'futures') {
        const result = await this.futuresOrder({
          type: 'MARKET',
          symbol: symbol,
          side: side,
          // price: markPrice,
          quantity: quantity,
          positionSide: positionSide,
          // timeInForce: "GTC",
        } as NewFuturesOrderParams);
        const [futuresBalanceMap, futuresPositions] = await Promise.all([this.getFuturesBalance(), this.getFuturesPositions(symbol)]);
        this.riskModel.updateBalance(futuresBalanceMap as Record<string, FuturesAccountBalance>, 'futures');
        this.riskModel.updatePositions(futuresPositions, 'futures');
        robot.markdown(
          `${type}`,
          `${type}: ${keepDecimalFixed(unRealizedProfit, 2)} U -> ${symbol}(${markPrice}) by ${this.getInfo().username}`
        );
      } else if (tradeType === 'delivery') {
        const result = await this.deliveryOrder({
          type: 'MARKET',
          symbol: symbol,
          side: side,
          // price: markPrice,
          quantity: quantity,
          positionSide: positionSide,
          // timeInForce: "GTC",
        } as NewFuturesOrderParams);
        const [futuresBalanceMap, futuresPositions] = await Promise.all([this.getDeliveryBalance(), this.getDeliverysPositions(symbol)]);
        this.riskModel.updateBalance(futuresBalanceMap as Record<string, FuturesAccountBalance>, 'delivery');
        this.riskModel.updatePositions(futuresPositions, 'delivery');
        robot.markdown(
          `${type}`,
          `${type}: ${unRealizedProfit} ${parserBaseSymbol(getSpotSymbol(symbol))} -> ${symbol}(${markPrice}) by ${this.getInfo().username}`
        );
      }
    });
    this.orderWatch.on('maybeFilled', async (order) => {
      outLogger.info('maybeFilled', order.symbol, order.price)
      if (order.tradeType === 'futures') {
        const [futuresBalanceMap, futuresPositions] = await Promise.all([this.getFuturesBalance(), this.getFuturesPositions(order.symbol)]);
        this.riskModel.updateBalance(futuresBalanceMap as Record<string, FuturesAccountBalance>, 'futures');
        this.riskModel.updatePositions(futuresPositions, 'futures');
      } else if (order.tradeType === 'delivery') {
        const [futuresBalanceMap, futuresPositions] = await Promise.all([this.getDeliveryBalance(), this.getDeliverysPositions(order.symbol)]);
        this.riskModel.updateBalance(futuresBalanceMap as Record<string, FuturesAccountBalance>, 'delivery');
        this.riskModel.updatePositions(futuresPositions, 'delivery');
      }
      // await this.getOrder({symbol: order.symbol, order_id: order.clientOrderId as string, trade_type: order.tradeType});
    });
    // this.getNotionalAndLeverageBrackets("ETHUSDT")
  }
  updateSDK({ access_key, secret_key, }: { access_key: string; secret_key: string; }) {
    this.mainClient = new MainClient({
      api_key: access_key,
      api_secret: secret_key,
    });
    this.futureClient = new USDMClient({
      api_key: access_key,
      api_secret: secret_key,
    });
    this.deliveryClient = new CoinMClient({
      api_key: access_key,
      api_secret: secret_key,
    });
  }

  /**
   * 获取余额
   * @param symbol usdt btc ht
   */
  getBalance = <T extends (string | undefined)>(baseAsset?: T) => {
    return this.mainClient.getBalances().then(balances => {
      balances.forEach(item => {
        this.spotBalanceMap[item.coin] = {
          free: item.free as string,
          locked: item.locked as string,
        };
      })
      return baseAsset ? this.spotBalanceMap[baseAsset] : this.spotBalanceMap;
    }).catch((err) => {
      errLogger.error(err);
      return this.spotBalanceMap
    })
  };
  /**
   * 获取合约余额
   * @param symbol usdt btc ht
   */
  getDeliveryBalance = (baseAsset?: string) => {
    // if (this.deliveryBalanceMap[baseAsset]) {
    //   return Promise.resolve(this.deliveryBalanceMap[baseAsset])
    // }
    return this.deliveryClient.getBalance().then(balances => {
      balances.forEach(item => {
        this.deliveryBalanceMap[item.asset] = item as SymbolBalance;
      });
      return baseAsset ? this.deliveryBalanceMap[baseAsset] : this.deliveryBalanceMap;
    }).catch(() => {
      return this.deliveryBalanceMap
    })
  };

  /**
   * 获取合约余额(usdt)
   * @param symbol usdt btc ht
   */
  getFuturesBalance = singlePromise(<T extends (string | undefined)>(baseAsset?: T) => {
    return this.futureClient.getBalance().then((balances) => {
      balances.forEach(item => {
        this.futuresBalanceMap[item.asset] = item as FuturesAccountBalance;
      });
      return baseAsset ? this.futuresBalanceMap[baseAsset] : this.futuresBalanceMap;
    }).catch(() => {
      return this.futuresBalanceMap
    })
  })

  async getFuturesPositions(symbol: string) {
    try {
      const list = await retryPromise(2, () =>
        this.futureClient.getPositions({
          symbol: symbol
        })
      );
      if (!Array.isArray(list)) {
        outLogger.error('futuresPositionRisk', (list as any).message);
        return []
      }
      return list
    } catch (error) {
      this.futureClient.syncTime();
      outLogger.error("futuresPositionRisk err", error);
      return [];
    }
  }
  async getDeliverysPositions(symbol: string) {
    try {
      if (!symbol.includes('_')) {
        const symbolInfo = await this.binanceCommon.getDeliverySymolInfo(symbol);
        symbol = symbolInfo.symbol;
      }
      const list = await retryPromise(2, () =>
        this.deliveryClient.getPositions()
      );
      if (!Array.isArray(list)) {
        outLogger.error('futuresPositionRisk', (list as any).message);
        return []
      }
      return list.filter(item => item.symbol === symbol);
    } catch (error) {
      this.futureClient.syncTime();
      outLogger.error("futuresPositionRisk err", error);
      return [];
    }
  }
  async getPositionAmt(contractSymbol: string) {
    const amountMap = {
      SHORT: 0,
      LONG: 0
    };
    const list = await this.getFuturesPositions(contractSymbol)

    list.forEach(item => {
      amountMap[item.positionSide] = Math.abs(
        Number(item.positionAmt)
      );
    });
    return amountMap;
  }
  async getDeliveryPositionAmt(contractSymbol?: string) {
    const amountMap = {
      SHORT: 0,
      LONG: 0,
      BOTH: 0,
    };
    try {
      const list = await this.getDeliverysPositions(contractSymbol);
      list.forEach(item => {
        amountMap[item.positionSide] += Math.abs(
          Number(item.positionAmt)
        );
      });
      return amountMap;
    } catch (error) {
      outLogger.error("deliveryPositionRisk", error);
    }
    return amountMap;
  }
  async getOrder(order: Pick<AutoOrderHistoryEntity, 'trade_type' | 'order_id' | 'symbol'>) {
    let data: OrderResult | SpotOrder  | undefined;
    try {
      if (order.trade_type?.includes("futures")) {
        data = await this.futureClient.getOrder({
          symbol: order.symbol as string,
          origClientOrderId: order.order_id
        });
      } else if (order.trade_type?.includes("delivery")) {
        data = await this.deliveryClient.getOrder({
          symbol: order.symbol as string,
          origClientOrderId: order.order_id
        });
      } else {
        data = await this.mainClient.getOrder({
          symbol: order.symbol as string,
          origClientOrderId: order.order_id
        });
      }
      return data;
    } catch (error) {
      errLogger.error(error.message);
    }
  }
  async getNotionalAndLeverageBrackets(symbol: string) {
    // TODO
    // https://binance-docs.github.io/apidocs/futures/cn/#user_data-10
    // {
    //   bracket: 5,
    //   initialLeverage: 20,
    //   notionalCap: 50000000,
    //   notionalFloor: 5000000,
    //   maintMarginRatio: 0.02,
    //   cum: 54300
    // },
    const data = await this.futureClient.getNotionalAndLeverageBrackets({symbol})
  }

  /**
   * 默认转全部
   * @param symbol 
   */
  async assetTransfer(params: Partial<UniversalTransferParams>) {
    // MAIN_UMFUTURE 现货钱包转向U本位合约钱包
    // MAIN_CMFUTURE 现货钱包转向币本位合约钱包

    // UMFUTURE_MAIN U本位合约钱包转向现货钱包

    // CMFUTURE_MAIN 币本位合约钱包转向现货钱包
    try {
      if (params.type === 'MAIN_UMFUTURE') {
        if (params.amount === undefined) {
          await this.getBalance();
          const amount = this.spotBalanceMap[params.asset]?.free;
          params.amount = Number(amount);
        }
      } else if (params.type === 'MAIN_CMFUTURE') {
        if (params.amount === undefined) {
          await this.getBalance();
          const amount = this.spotBalanceMap[params.asset]?.free;
          params.amount = Number(amount);
        }
      } else if (params.type === 'UMFUTURE_MAIN') {
        params.asset === 'USDT';
        if (params.amount === undefined) {
          await this.getFuturesBalance();
          const amount = this.futuresBalanceMap[params.asset]?.availableBalance;
          params.amount = Number(amount);
        }
      } else if (params.type === 'CMFUTURE_MAIN') {
        if (params.amount === undefined) {
          await this.getDeliveryBalance();
          const amount = this.deliveryBalanceMap[params.asset]?.availableBalance;
          params.amount = Number(amount);
        }
      }
      await this.mainClient.submitUniversalTransfer(params as UniversalTransferParams);
    } catch (error) {
      errLogger.error(error.message)
    }
  }
  async submitOrder(params: {
    symbol: string;
    tradeType: AutoTradeType;
    orderType: AutoOrderConfig['order_type'];
    action: OrderSide;
    positionSide?: PositionSide;
    depthRangeDis?: number;
    orderConfig: AutoOrderConfig;
    positionsRate?: number;
    quantity?: number;
    price?: number;
  }) {
    const { orderConfig, symbol, action, quantity, price, orderType, tradeType, positionSide } = params;
    const newOrderConfig = {
      ...orderConfig,
      quantity,
      price,
      trade_type: tradeType,
      order_type: orderType,
      positionSide
    }

    if (tradeType === 'spot') {
      return this.beforeSpotOrder(symbol, action, newOrderConfig)
    } else if (tradeType === 'futures') {
      return this.futuresOrder({
        symbol,
        quantity: quantity,
        side: action,
        price: price,
        positionSide: positionSide,
        type: orderType || "LIMIT",
        timeInForce: orderType === "MARKET" ? undefined : "GTC",
      } as NewFuturesOrderParams);
    } else if (tradeType === 'delivery') {
      const symbolInfo = await this.binanceCommon.getDeliverySymolInfo(symbol);
      if (params.quantity) {
        params.quantity = this.binanceCommon.baseCountToCount(params.symbol, params.price, params.quantity);
        outLogger.info("params.quantity1", params.quantity)
      }
      return this.deliveryOrder({
        symbol: symbolInfo.symbol,
        quantity: params.quantity,
        side: action,
        price: price,
        positionSide: positionSide,
        type: orderType || "LIMIT",
        timeInForce: orderType === "MARKET" ? undefined : "GTC",
      } as NewFuturesOrderParams);
    } 
  }
  async beforeSpotOrder(symbol: string, action: "BUY" | "SELL", tradeConfig: Pick<AutoOrderConfig, 'order_type' | 'buy_usdt' | 'sell_usdt'> & {quantity?: number, price?: number,}) {
    const priceIndex = binancePriceConverter.getPriceIndex(symbol);
    const base = parserBaseSymbol(symbol);
    const quote = parserQuoteSymbol(symbol);
    const currentPrice = this.dataHander.getQuotePrice(base);
    const symbolInfo = await this.binanceCommon.getSymbolInfo(symbol);
    outLogger.info("beforeSpotOrder currentPrice", currentPrice);
    try {
      await this.getBalance();
    } catch (error) {
      outLogger.warn("order getBalance()", error.message);
    }
    if (this.spotBalanceMap[quote] === undefined) {
      outLogger.error(`this.spotBalanceMap ${quote}`);
      return
    }

    const quoteCurrencyBalance = this.spotBalanceMap[quote].free;
    const baseCurrencyBalance = this.spotBalanceMap[base].free;
    const pricePoolFormDepth = this.dataHander.orderBook.getTracePrice(symbol);
    const price =  tradeConfig.price ? tradeConfig.price : action === "BUY" ? pricePoolFormDepth.buy[0] : pricePoolFormDepth.sell[0]
    const usdt = tradeConfig.quantity 
    ? tradeConfig.quantity 
    : action === "BUY" ? tradeConfig.buy_usdt : tradeConfig.sell_usdt;
    let quantity =  keepDecimalFixed(usdt / price, (symbolInfo.baseAssetPrecision / 2 | 0));
    const hasEnoughBalance = Number(quoteCurrencyBalance) > usdt * 1.002;
    const hasEnoughAmount = Number(baseCurrencyBalance) > quantity;

    outLogger.info(
      `${base}: ${baseCurrencyBalance}, ${quote}: ${quoteCurrencyBalance}`
    );

    if (action === "BUY" && Number(quoteCurrencyBalance) < (Number(symbolInfo.minQty) * price)) {
      outLogger.info(`quote-currency( ${quote} ) not enough, ${Number(symbolInfo.minQty) * price}`);
      return;
    } else if (action === "SELL" && baseCurrencyBalance < symbolInfo.minQty) {
      outLogger.info(`base-currency( ${base} ) not enough`);
      return;
    }
    const clientOrderId = generateNewOrderId('spot');
    if (action === 'BUY' && !hasEnoughBalance) {
      quantity = autoToFixed(Number(quoteCurrencyBalance) / price)
    } else  if (action === 'SELL' && !hasEnoughAmount) {
      quantity = keepDecimalFixed(baseCurrencyBalance, (symbolInfo.baseAssetPrecision / 2 | 0))
    }
    outLogger.info(`quantity: ${quantity}`)
    const params: NewSpotOrderParams = {
      symbol,
      side: action,
      quantity: quantity,
      price: price,
      type: tradeConfig.order_type || "LIMIT",
      newClientOrderId: clientOrderId,
      timeInForce: "GTC",
    }

    return await this.spotOrder(params)
  }
  async beforeFuturesOrder(symbol: string, action: "BUY" | "SELL", tradeConfig: AutoOrderConfig & {quantity?: number, price?: number}) {
    const [price, balance, positionAmtMap, symbolInfo] = await Promise.all([
      this.binanceCommon.getFuturesPrice(symbol),
      this.getFuturesBalance(),
      this.getPositionAmt(symbol),
      this.binanceCommon.getFuturesSymbolInfo(symbol),
      this.futureClient.setLeverage({ symbol, leverage: tradeConfig.lever_rate })
    ]);
    const { minQty } = symbolInfo
    const usdtBalance = Number(this.futuresBalanceMap["USDT"].availableBalance);
    // 未实现盈亏
    const usdtCrossUnPnl = Number(this.futuresBalanceMap["USDT"].crossUnPnl);

    const pricePoolFormDepth = this.dataHander.futruesOrderBook.getTracePrice(symbol, 0.09);
    // {
    //   sell: [ 2007, 2016, 2018, 2040, 2140, 2150 ],
    //   buy: [ 1935, 1904, 1888, 1883, 1830, 1700 ]
    // }

    const params: Partial<NewFuturesOrderParams> = {
      symbol: symbol,
      price: tradeConfig.price ? tradeConfig.price : action === "BUY" ? Math.max(...pricePoolFormDepth.buy) : Math.min(...pricePoolFormDepth.sell),
      quantity: 0,
      side: action,
    };
    if (pricePoolFormDepth.buy.length === 0 || pricePoolFormDepth.sell.length === 0) {
      outLogger.warn('beforeFuturesOrder ->', pricePoolFormDepth);
      // params.price = action === "BUY" ? price * 0.999 : price * 1.001;
      params.price = price;
    } else {
      outLogger.info('beforeFuturesOrder ->', params.side, params.price, pricePoolFormDepth)
    }
    outLogger.info(`userid: ${this.getUserId()} ${symbol} usdtBalance: ${usdtBalance}, usdtCrossUnPnl: ${usdtCrossUnPnl} ${JSON.stringify(positionAmtMap)}}`)

    if (action === "BUY") {
      // 平空
      if (tradeConfig.close_short > 0 && positionAmtMap.SHORT > Number(minQty)) {
        return this.futuresOrder({
          ...params,
          type: "MARKET",
          quantity: tradeConfig.quantity ? tradeConfig.quantity : tradeConfig.close_short,
          positionSide: 'SHORT',
        } as NewFuturesOrderParams)
      }
      // 开多 
      if (tradeConfig.open_long > 0 && usdtBalance > (tradeConfig.open_long * Number(price)) / tradeConfig.lever_rate) {
        return this.futuresOrder({
          ...params,
          quantity: tradeConfig.quantity ? tradeConfig.quantity : tradeConfig.open_long,
          positionSide: 'LONG',
          type: tradeConfig.order_type || "LIMIT",
          timeInForce: tradeConfig.order_type === "MARKET" ? undefined : "GTC",
        } as NewFuturesOrderParams);
      }
    } else {
      // 卖出平多
      if (tradeConfig.close_long > 0 && positionAmtMap.LONG > Number(minQty)) {
        return this.futuresOrder({
          ...params,
          type: "MARKET",
          quantity: tradeConfig.quantity ? tradeConfig.quantity : tradeConfig.close_long,
          positionSide: 'LONG',
        } as NewFuturesOrderParams)
      }
      if (tradeConfig.open_short > 0 && usdtBalance > (tradeConfig.open_short * Number(price)) / tradeConfig.lever_rate) {
        // 卖出开空
        return this.futuresOrder({
          ...params,
          quantity: tradeConfig.quantity ? tradeConfig.quantity : tradeConfig.open_short,
          positionSide: 'SHORT',
          type: tradeConfig.order_type || "LIMIT",
          timeInForce: tradeConfig.order_type === "MARKET" ? undefined : "GTC",
        } as NewFuturesOrderParams);
      }
    }

    
  }
  async beforeDeliveryOrder(symbol: string, action: "BUY" | "SELL", tradeConfig: AutoOrderConfig & {quantity?: number, price?: number}) {
    const symbolInfo = await this.binanceCommon.getDeliverySymolInfo(symbol);

    const [price, balance, positionAmtMap] = await Promise.all([
      this.binanceCommon.getDeliveryPrice(symbolInfo.symbol),
      this.getDeliveryBalance(),
      this.getDeliveryPositionAmt(symbolInfo.symbol),

    ]);
    const { availableBalance, crossUnPnl } = this.deliveryBalanceMap[parserBaseSymbol(symbol)];

    const params = {
      symbol: symbolInfo.symbol,
      price: action === "BUY" ? autoToFixed(price * 0.99) : autoToFixed(price * 1.01),
      quantity: 0,
      side: action,
      positionSide: '',
    };
    outLogger.info(`${symbol} availableBalance: ${availableBalance}, crossUnPnl: ${crossUnPnl} ${JSON.stringify(positionAmtMap)}}`)
    if (action === "BUY") {
      // 开多 
      if (tradeConfig.open_long > 0) {
        return this.deliveryOrder({
          ...params,
          quantity: this.binanceCommon.baseCountToCount(params.symbol, params.price, tradeConfig.quantity ? tradeConfig.quantity : tradeConfig.open_long),
          positionSide: 'LONG',
          type: tradeConfig.order_type || "LIMIT",
          timeInForce: tradeConfig.order_type === "MARKET" ? undefined : "GTC",
        } as NewFuturesOrderParams);
      } 
      // 平空
      if (tradeConfig.close_short > 0 && positionAmtMap.SHORT > 0) {
        return this.deliveryOrder({
          ...params,
          type: "MARKET",
          quantity: this.binanceCommon.baseCountToCount(params.symbol, params.price, tradeConfig.quantity ? tradeConfig.quantity : tradeConfig.close_short),
          positionSide: 'SHORT',
        } as NewFuturesOrderParams)
      }
    } else if (action === "SELL") {
      if (tradeConfig.open_short > 0) {
        // 卖出开空
        return this.deliveryOrder({
          ...params,
          quantity: this.binanceCommon.baseCountToCount(params.symbol, params.price, tradeConfig.quantity ? tradeConfig.quantity : tradeConfig.open_short),
          positionSide: 'SHORT',
          type: tradeConfig.order_type || "LIMIT",
          timeInForce: tradeConfig.order_type === "MARKET" ? undefined : "GTC",
        } as NewFuturesOrderParams);
      }
      // 卖出平多
      if (tradeConfig.close_long > 0 && positionAmtMap.LONG > 0) {
        return this.deliveryOrder({
          ...params,
          type: "MARKET",
          quantity: this.binanceCommon.baseCountToCount(params.symbol, params.price, tradeConfig.close_long),
          positionSide: 'LONG',
        } as NewFuturesOrderParams)
      }
    }

  }
  async futuresOrder(params: Parameters<USDMClient['submitNewOrder']>[0]): Promise<NewOrderResult> {
    try {
      if (params.type === 'MARKET') {
        delete params.price;
        delete params.timeInForce;
      }

      const clientOrderId = generateNewOrderId('usdm');
      const data = await retryPromise(2, () =>
        this.futureClient.submitNewOrder({
          newClientOrderId: clientOrderId,
          ...params,
        })
      );
      // {
      //   orderId: 20853445922,
      //   symbol: 'TRBUSDT',
      //   status: 'NEW',
      //   clientOrderId: 'x-15PC4ZJy5BKtggXQimt5TWztZ4DZa17vW',
      //   price: '70.149',
      //   avgPrice: '0.00',
      //   origQty: '53.0',
      //   executedQty: '0.0',
      //   cumQty: '0.0',
      //   cumQuote: '0.0000',
      //   timeInForce: 'GTC',
      //   type: 'LIMIT',
      //   reduceOnly: true,
      //   closePosition: false,
      //   side: 'BUY',
      //   positionSide: 'SHORT',
      //   stopPrice: '0.000',
      //   workingType: 'CONTRACT_PRICE',
      //   priceProtect: false,
      //   origType: 'LIMIT',
      //   priceMatch: 'NONE',
      //   selfTradePreventionMode: 'NONE',
      //   goodTillDate: 0,
      //   updateTime: 1714805400088
      // }
      // outLogger.info('futuresOrder', data)
      this.orderAfter({
        ...params,
        orderType: params.type as AutoOrderConfig['order_type'],
        quantity: params.quantity,
        tradeType: 'futures',
        orderId: clientOrderId,
        price: (data as any).price || params.price,
        action: params.side + "-" + params.positionSide,
        status: (data as NewOrderResult).status,
        raw: {
          orderId: data.orderId,// 系统订单号
          price: data.price,
          avgPrice: data.avgPrice,
          cumQuote: data.cumQuote,
          executedQty: data.executedQty
        }
      });
      return data;
    } catch (error) {
      outLogger.error('futuresOrder', error.message, params)
    }
  }
  // async closeAllFuturesOrder(symbol: string) {
  //   const positions = await this.futureClient.getPositions({
  //     symbol: symbol,
  //   })

  //   for (const order of positions) {
  //     if (Math.abs(Number(order.positionAmt)) <= 0) {
  //       continue
  //     }
  //     const direction = order.positionSide === 'LONG' ? 'SELL' : 'BUY'
  //     await this.futuresOrder({
  //       symbol: symbol,
  //       side: direction,
  //       type: 'MARKET',
  //       positionSide: order.positionSide,
  //       quantity: Math.abs(Number(order.positionAmt)),
  //     })
  //   }
  //   this.orderWatch.clear(symbol, 'futures');
  // }
  // async stopFuturesOrder(
  //   symbol: string,
  //   positionSide: "LONG" | "SHORT",
  // ): Promise<void> {
  //   const futuresPositions = await this.getFuturesPositions(symbol);
  //   for (let i = 0; i < futuresPositions.length; i++) {
  //     const element = futuresPositions[i];
  //     if (element.positionSide === positionSide) {
  //       const action = element.positionSide === 'SHORT' ? "BUY" : "SELL"
  //       await this.safeOrder(symbol, action, positionSide, {
  //         stop: true,
  //         trade_type: 'futures',
  //       });
  //     }
  //   }
  // }
  // async stopDeliveryOrder(
  //   symbol: string,
  //   positionSide: "LONG" | "SHORT",
  // ): Promise<void> {
  //   const futuresPositions = await this.getDeliverysPositions(symbol);
  //   for (let i = 0; i < futuresPositions.length; i++) {
  //     const element = futuresPositions[i];
  //     if (element.positionSide === positionSide && Math.abs(Number(element.positionAmt)) > 0) {
  //       const action = element.positionSide === 'SHORT' ? "BUY" : "SELL"
  //       await this.safeOrder(element.symbol, action, positionSide, {
  //         stop: true,
  //         trade_type: 'delivery',
  //       });
  //     }
  //   }
  // }
  async spotOrder(params: NewSpotOrderParams) {
    if (params.type === 'MARKET') {
      delete params.timeInForce;
      delete params.price;
    }

    try {
      const data = await retryPromise(2, () =>
        this.mainClient.submitNewOrder(params)
      );
      this.orderAfter({ 
        orderId: params.newClientOrderId, 
        symbol: params.symbol,
        orderType: params.type as AutoOrderConfig['order_type'],
        quantity: params.quantity,
        tradeType: "spot", 
        price: (data as OrderResponseResult).price, 
        action: params.side, 
        status: (data as OrderResponseResult).status,
        raw: {
          orderId: data.orderId,// 系统订单号
        }
      })
    } catch (error) {
      outLogger.error("spotOrder", error.message);
    }
  }
  async deliveryOrder(params: Parameters<CoinMClient['submitNewOrder']>[0]): Promise<void> {
    try {
      if (params.type === 'MARKET') {
        delete params.price;
        delete params.timeInForce;
      }
      const clientOrderId = generateNewOrderId('coinm');
      const data = await retryPromise(2, () =>
        this.deliveryClient.submitNewOrder({
          ...params,
          newClientOrderId: clientOrderId,
        })
      );

      this.orderAfter({
        ...params,
        orderType: params.type as AutoOrderConfig['order_type'],
        quantity: params.quantity,
        tradeType: 'delivery',
        orderId: clientOrderId,
        price: (data as any).price || params.price,
        action: params.side + "-" + params.positionSide,
        status: (data as NewOrderResult).status,
        raw: {
          symbol: params.symbol,
          orderId: data.orderId, // 系统订单号
          price: data.price,
          avgPrice: data.avgPrice,
          cumQuote: data.cumQuote,
          executedQty: data.executedQty
        }
      })
    } catch (error) {
      outLogger.error('deliveryOrder', error.message)
    }

  }
  // async closeAllDeliveryOrder(symbol: string) {
  //   const positions = await this.deliveryClient.getPositions()

  //   for (const order of positions) {
  //     if (order.symbol !== symbol) {
  //       continue
  //     }
  //     if (Math.abs(Number(order.positionAmt)) <= 0) {
  //       continue
  //     }
  //     const direction = order.positionSide === 'LONG' ? 'SELL' : 'BUY'
  //     await this.deliveryOrder({
  //       symbol: symbol,
  //       side: direction,
  //       type: 'MARKET',
  //       positionSide: order.positionSide,
  //       quantity: Math.abs(Number(order.positionAmt)),
  //     });
  //   }
  //   this.orderWatch.clear(symbol, 'delivery');
  // }
  async cancelOrder(
    symbol: string,
    orderId: string,
    tradeType: AutoTradeType,
  ) {
    try {
      let cancelData: CancelFuturesOrderResult | CancelSpotOrderResult;
      if (tradeType.includes("futures")) {
        cancelData = await this.futureClient.cancelOrder({ symbol, origClientOrderId: orderId });
      } else if (tradeType.includes("delivery")) {
        cancelData = await this.deliveryClient.cancelOrder({ symbol, origClientOrderId: orderId });
      } else {
        cancelData = await this.mainClient.cancelOrder({ symbol, origClientOrderId: orderId });
      }
      this.orderWatch.removeOrder({ symbol, clientOrderId: orderId, tradeType: tradeType });
      return cancelData
    } catch (error) {
      if (error.body.code == -2011) {
        this.orderWatch.removeOrder({ symbol, clientOrderId: orderId, tradeType: tradeType });
        return {
          status: 'CANCELED'
        }
      }
      errLogger.error('cancelOrder', error, error.body || error.message)
      return error
    }
  }
  async safeOrder(
    symbol: string,
    action: "BUY" | "SELL",
    positionSide: "LONG" | "SHORT",
    options = {} as {
      stop?: boolean
      trade_type?: AutoTradeType
    }
  ) {
    const contractSymbol = getSpotSymbol(symbol);
    const tradeConfig = await SymbolOrderConfigService.findOne({ userid: this.getUserId(), symbol: symbol });
    if (!tradeConfig || !tradeConfig.enabled) {
      return;
    }
    const { lever_rate } = tradeConfig;
    const trade_type = options.trade_type || tradeConfig.trade_type;
    // 平单
    const CLOSE =
      (action === "SELL" && positionSide === "LONG") ||
      (action === "BUY" && positionSide === "SHORT");
    const STOP = options.stop;

    let quoteCurrencyBalance = 0;
    let balance = 0;
    let currentPrice = 0;
    let symbolInfo: FuturesSymbolInfo | SymbolInfo | DeliverySymbolInfo;
    let pricePoolFormDepth = {
      sell: [] as number[],
      buy: [] as number[]
    }

    if (trade_type === "futures") {
      const [futuresSymbolInfo, price, _] = await Promise.all([
        this.binanceCommon.getFuturesSymbolInfo(contractSymbol),
        this.binanceCommon.getFuturesPrice(contractSymbol),
        this.getFuturesBalance()
      ]);
      symbolInfo = futuresSymbolInfo;
      currentPrice = price;
      pricePoolFormDepth = this.dataHander.futruesOrderBook.getTracePrice(symbol, 0.1);
      let positionAmt = 0;
      if (CLOSE) {
        const positionAmtMap = await this.getPositionAmt(contractSymbol);
        positionAmt = positionAmtMap[positionSide];
      }
      quoteCurrencyBalance = Number(this.futuresBalanceMap["USDT"].availableBalance) * 0.5;
      balance = CLOSE ? Math.abs(positionAmt) : (quoteCurrencyBalance / currentPrice) * lever_rate;

    } else if (trade_type === "delivery") {
      const orderBook = new OrderBook();
      symbolInfo = await this.binanceCommon.getDeliverySymolInfo(contractSymbol);
      const [depth, deliveryBalanceMap] = await Promise.all([
        this.binanceCommon.deliveryDepth((symbolInfo as DeliverySymbolInfo).symbol),
        this.getDeliveryBalance(),
      ]);
      
      let positionAmt = 0;
      if (CLOSE) {
        const positionAmtMap = await this.getDeliveryPositionAmt((symbolInfo as DeliverySymbolInfo).symbol);
        positionAmt = positionAmtMap[positionSide];
      }
      quoteCurrencyBalance = Number(this.deliveryBalanceMap[parserBaseSymbol(symbol)].availableBalance);
      orderBook.process({
        symbol: symbol,
        bids: depth.bids as any[],
        asks: depth.asks as any[],
        timestamp: depth.E,
      });
      pricePoolFormDepth = orderBook.getTracePrice(symbol, 0.1);
      balance = CLOSE ? Math.abs(positionAmt) : (quoteCurrencyBalance / currentPrice) * lever_rate;
    } else if (trade_type === "spot") {
      pricePoolFormDepth = this.dataHander.orderBook.getTracePrice(symbol, 0.1);
      symbolInfo = await this.binanceCommon.getSymbolInfo(symbol)
      await this.getBalance();
      const base = parserBaseSymbol(symbol);
      const quote = parserQuoteSymbol(symbol);
      const quoteCurrencyBalance = Number(this.spotBalanceMap[quote].free);
      const baseCurrencyBalance = Number(this.spotBalanceMap[base].free);
      balance = action == "SELL" ? Math.abs(baseCurrencyBalance) : (quoteCurrencyBalance / currentPrice);
    }

    const orders = splitOrder({
      currentPrice: currentPrice,
      prices: action === "BUY"
        ? STOP ? [pricePoolFormDepth.buy[1]] : pricePoolFormDepth.buy
        : STOP ? [pricePoolFormDepth.sell[1]] : pricePoolFormDepth.sell,
      minVolume: Number(symbolInfo.minQty) * 10,
      balance: balance
    })

    outLogger.info(
      "pricePoolFormDepth:",
      Number(symbolInfo.minQty) * 10,
      pricePoolFormDepth
    );
    outLogger.info("orders:", orders);
    for (const { price, volume } of orders) {
      if (trade_type === "futures") {
        await this.futuresOrder({
          symbol: contractSymbol,
          price: keepDecimalFixed(price, (symbolInfo as FuturesSymbolInfo).pricePrecision),
          quantity: keepDecimalFixed(volume, (symbolInfo as FuturesSymbolInfo).quantityPrecision),
          side: action,
          positionSide: positionSide,
          type: "LIMIT",
          timeInForce: "GTC"
        });
      } else if (trade_type === "delivery") {
        await this.deliveryOrder({
          symbol: symbol,
          price: keepDecimalFixed(price, (symbolInfo as DeliverySymbolInfo).pricePrecision),
          quantity: keepDecimalFixed(volume, (symbolInfo as DeliverySymbolInfo).quantityPrecision),
          side: action,
          positionSide: positionSide,
          type: "LIMIT",
          timeInForce: "GTC"
        });
      } else if (trade_type === "spot") {
        await this.spotOrder({
          symbol,
          price: keepDecimalFixed(price, (symbolInfo as SymbolInfo).quoteAssetPrecision),
          quantity: keepDecimalFixed(volume, (symbolInfo as SymbolInfo).baseAssetPrecision),
          side: action,
          type: 'LIMIT',
          timeInForce: "GTC"
        });
      }
    }
  }

  destroy() {
    super.destroy();
 
    this.mainClient = null;
    this.futureClient = null;
    this.deliveryClient = null;

    this.spotBalanceMap = null;
    this.deliveryBalanceMap = null;
    this.futuresBalanceMap = null;
    this.binanceCommon = null;
  }
}
